package vfs.common.file;

import vfs.common.User;

import java.io.*;
import java.util.*;

/**
 * User: serge
 * Date: 21.11.13
 * Time: 15:27
 */
public class VirtualFileSystem
{
    private final String DUMP_FILE_NAME = "fsdump";
    private final String PATH_SEPARATOR = "\\\\";
    private final String ROOT_NAME = "c:";

    private VirtualFile root;
    private Map<String, User> users;


    public void setUsers(Map<String, User> users)
    {
        this.users = users;
    }

    /**
     * Return file from filesystem by path
     *
     * @param pathString
     * @param user
     * @return
     */
    public VirtualFile locateFile(String pathString, boolean directory, User user) throws VirtualFileNotFoundException
    {
        checkRootAvailable();
        List<String> pathAbs = getPathAbs(pathString, user);
        return locateFile(pathAbs, directory);
    }

    /**
     * Return file from filesystem by path no matter dir or not (for copy)
     *
     * @param pathString
     * @param user
     * @return
     */
    public VirtualFile locateFile(String pathString, User user) throws VirtualFileNotFoundException
    {
        checkRootAvailable();
        List<String> pathAbs = getPathAbs(pathString, user);

        VirtualFile file;
        try
        {
            file = locateFile(pathAbs, true);
        }
        catch (VirtualFileNotFoundException e)
        {
            file = locateFile(pathAbs, false); // :)
        }

        return file;
    }

    /** The only function writes changes to FS so collisions can be made only here. Synchronizing it */
    public synchronized void makeFile(String pathString, boolean directory, User user) throws VirtualFileNotFoundException,
            VirtualFileAlreadyExistsException
    {
        List<String> pathAbs = getPathAbs(pathString, user);
        String fileName = pathAbs.remove(pathAbs.size() - 1);

        VirtualFile parentDir = locateFile(pathAbs, true);
        Map<String, VirtualFile> dirContents = parentDir.getData();

        if (dirContents.containsKey(fileName))
        {
            throw new VirtualFileAlreadyExistsException();
        }
        else
        {
            VirtualFile file = new VirtualFile(directory, fileName);
            dirContents.put(file.getName(), file);
        }
    }

    /** Persists changes in dump file. Order of threads to write doesnot matter, the success of write does */
    public synchronized void serialize() throws IOException
    {
        checkRootAvailable();

        FileOutputStream fos = new FileOutputStream(DUMP_FILE_NAME);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(root);
        oos.flush();
        oos.close();
        fos.close();
    }

    /** Unserializes fs from dump file */
    public void unserialize() throws FileNotFoundException, IOException, ClassNotFoundException
    {
        FileInputStream fis = new FileInputStream(DUMP_FILE_NAME);
        ObjectInputStream ois = new ObjectInputStream(fis);

        root = (VirtualFile) ois.readObject();

        ois.close();
        fis.close();
    }

    /** to use on first start */
    public void newRoot()
    {
        root = new VirtualFile(true, ROOT_NAME);
    }

    public void changeDir(User user, String path) throws VirtualFileNotFoundException
    {
        locateFile(path, true, user);

        List<String> pathAbs = getPathAbs(path, user);
        users.get(user.getName()).setCd(pathAbs);
    }

    private VirtualFile locateFile(List<String> pathAbs, boolean directory) throws VirtualFileNotFoundException
    {
        checkRootAvailable();

        VirtualFile dirCurrent = root;

        Iterator<String> pathIterator = pathAbs.iterator();

        /* removing c: from path list */
        pathIterator.next();

        if (!pathIterator.hasNext())
        {
            /* requested only root */
            return root;
        }

        while (pathIterator.hasNext())
        {
            String filenameSearch = pathIterator.next();

            Map<String, VirtualFile> dirContents = dirCurrent.getData();

            if (dirContents.containsKey(filenameSearch))
            {
                /* file exists in current directory */
                VirtualFile fileSearch = dirContents.get(filenameSearch);

                if (pathIterator.hasNext())
                {
                    /* going deeper */
                    if (fileSearch.isD())
                    {
                        dirCurrent = fileSearch;
                    }
                    else
                    {
                        /* stuck in a file cant go deeper */
                        throw new VirtualFileNotFoundException();
                    }
                }
                else
                {
                    /* path ended need return */
                    if (fileSearch.isD() == directory)
                    {
                        /* search file fully matches and returned */
                        return fileSearch;
                    }
                }
            }
            else
            {
                /* file not found */
                throw new VirtualFileNotFoundException();
            }
        }

        throw new VirtualFileNotFoundException();
    }

    private List<String> getPathAbs(String pathString, User user)
    {
        checkUsersAvailable();

        /* path incoming array */
        String[] pathInArray = pathString.split(PATH_SEPARATOR);
        List<String> pathAbs = new ArrayList<String>();

        /* is incoming path relative */
        if (!pathInArray[0].equals(ROOT_NAME))
        {
            /* existing current dir */
            List<String> cdir = users.get(user.getName()).getCd();

            if (cdir == null)
            {
                /* got no cd - abs path must start with root */
                pathAbs.add(ROOT_NAME);
            }
            else
            {
                pathAbs.addAll(cdir);
            }
        }

        /* now we got pathAbs to file needed to locate */
        Collections.addAll(pathAbs, pathInArray);

        return pathAbs;
    }

    private void checkRootAvailable()
    {
        if (root == null)
        {
            throw new IllegalStateException("No fs root found. Fs should be unserialized first");
        }
    }

    private void checkUsersAvailable()
    {
        if (users == null)
        {
            throw new IllegalStateException("No user list found. Should be set first");
        }
    }
}
